/*
 * Copyright 2007 Agile Plugins Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.agileplugins.monkey;

import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiAssignmentExpression;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiDeclarationStatement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiExpressionList;
import com.intellij.psi.PsiExpressionStatement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLocalVariable;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiStatement;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeCastExpression;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.refactoring.rename.RenameProcessor;
import com.intellij.util.IncorrectOperationException;

public class MockExpectsIntention implements IntentionAction {
    boolean createReturnType;
    String returnTypeName;
    private Logger logger = Logger.getInstance(getClass().getName());
    private PsiMethodCallExpression methodCallExpression;
    private PsiStatement statement;
    private PsiType returnType;
    private PsiExpression qualifierExpression;

    public String getText() {
        return "Convert to an expectation";
    }

    public String getFamilyName() {
        return "PurpleMonkeyDishwasher";
    }

    public boolean isAvailable(Project project, Editor editor, PsiFile psiFile) {
        int offset = editor.getCaretModel().getOffset();
        PsiElement element = psiFile.findElementAt(offset);
        if (element instanceof PsiWhiteSpace) {
            element = element.getPrevSibling();
        }
        statement = getStatement(element);
        if (statement == null) {
            return false;
        }
        createReturnType = false;
        returnType = null;
        returnTypeName = null;
        PsiExpression mainExpression = null;
        if (statement instanceof PsiExpressionStatement) {
            PsiExpressionStatement exp = (PsiExpressionStatement) statement;
            mainExpression = exp.getExpression();
            if (mainExpression instanceof PsiAssignmentExpression) {
                PsiAssignmentExpression ass = (PsiAssignmentExpression) mainExpression;
                PsiExpression lExpression = ass.getLExpression();
                returnTypeName = lExpression.getText();
                createReturnType = false;
                mainExpression = ass.getRExpression();
            } else {
                createReturnType = true;
                returnTypeName = generateVariable(mainExpression);
            }
        } else if (statement instanceof PsiDeclarationStatement) {
            PsiDeclarationStatement dec = (PsiDeclarationStatement) statement;
            PsiElement[] declaredElements = dec.getDeclaredElements();
            if (declaredElements.length == 1) {
                PsiElement declaredElement = declaredElements[0];
                if (declaredElement instanceof PsiLocalVariable) {
                    PsiLocalVariable loc = (PsiLocalVariable) declaredElement;
                    mainExpression = loc.getInitializer();
                    returnType = loc.getType();
                    returnTypeName = loc.getName();
                    createReturnType = true;
                }
            }
        }
        if (mainExpression instanceof PsiTypeCastExpression) {
            mainExpression = ((PsiTypeCastExpression) mainExpression).getOperand();
        }
        if (mainExpression instanceof PsiMethodCallExpression) {
            methodCallExpression = (PsiMethodCallExpression) mainExpression;
            PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
            qualifierExpression = methodExpression.getQualifierExpression();
            if (qualifierExpression instanceof PsiMethodCallExpression) {
                PsiMethodCallExpression psiMethodCallExpression = (PsiMethodCallExpression) qualifierExpression;
                PsiReferenceExpression psiReferenceExpression = psiMethodCallExpression.getMethodExpression();
                if (psiReferenceExpression.getText().equals("getMockInstanceProvider")) {
                    return true;
                }
            } else if (qualifierExpression instanceof PsiReferenceExpression) {
                PsiReferenceExpression refExp = (PsiReferenceExpression) qualifierExpression;
                PsiElement psiElement = refExp.resolve();
                if (psiElement instanceof PsiField) {
                    PsiField psiField = (PsiField) psiElement;
                    PsiModifierList modifierList = psiField.getModifierList();
                    return !modifierList.hasModifierProperty("static") && !modifierList.hasModifierProperty("final");
                } else if (psiElement instanceof PsiLocalVariable) {
                    PsiLocalVariable psiLocalVariable = (PsiLocalVariable) psiElement;
                    PsiExpression initializer = psiLocalVariable.getInitializer();
                    if (initializer instanceof PsiTypeCastExpression) {
                        PsiTypeCastExpression psiTypeCastExpression = (PsiTypeCastExpression) initializer;
                        PsiExpression operand = psiTypeCastExpression.getOperand();
                        if (operand instanceof PsiMethodCallExpression) {
                            PsiMethodCallExpression psiMethodCallExpression = (PsiMethodCallExpression) operand;
                            PsiReferenceExpression methodExpression2 = psiMethodCallExpression.getMethodExpression();
                            PsiElement psiElement2 = methodExpression2.resolve();
                            if (psiElement2 instanceof PsiMethod) {
                                PsiMethod psiMethod = (PsiMethod) psiElement2;
                                if ("proxy".equals(psiMethod.getName()) &&
                                        "Mock".equals(psiMethod.getContainingClass().getName())) {
                                    qualifierExpression = methodExpression2.getQualifierExpression();
                                    return true;
                                } else if ("mockObject".equals(psiMethod.getName())) {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }
        return false;
    }

    private String generateVariable(PsiExpression mainExpression) {
        PsiType type = mainExpression.getType();
        if (type == null) {
            return "value";
        }
        return lcFirst(type.getPresentableText());
    }

    public PsiStatement getStatement(PsiElement element) {
        while (element != null && !(element instanceof PsiStatement)) {
            element = element.getParent();
        }
        return (PsiStatement) element;
    }

    private String getNewName(PsiField field) {
        return "mock" + ucFirst(field.getName());
    }

    private String ucFirst(String name) {
        return name.substring(0, 1).toUpperCase() + name.substring(1);
    }

    public void invoke(Project project, Editor editor, PsiFile psiFile) throws IncorrectOperationException {
        PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
        if (qualifierExpression instanceof PsiReferenceExpression) {
            PsiReferenceExpression psiReferenceExpression = (PsiReferenceExpression) qualifierExpression;
            PsiElement psiElement = psiReferenceExpression.resolve();
            if (psiElement instanceof PsiField) {
                PsiField psiField = (PsiField) psiElement;
                String fieldName = psiField.getName();
                if (fieldName != null && !fieldName.startsWith("mock")) {
                    RenameProcessor renameprocessor = new RenameProcessor(project, psiField, getNewName(psiField),
                            false, false);
                    renameprocessor.run();
                }
            }
        }
        String referenceName = methodExpression.getReferenceName();
        StringBuffer sb = new StringBuffer();
        sb.append("expectOneCallTo(");
        sb.append('"').append(referenceName).append("\", ");
        sb.append(qualifierExpression.getText());
        sb.append(")");
        PsiMethod psiMethod = (PsiMethod) methodExpression.resolve();
        PsiParameterList parameterList = psiMethod.getParameterList();
        PsiParameter[] parameters = parameterList.getParameters();
        if (parameters.length > 0) {
            sb.append(".with(");
            PsiExpressionList argumentList = methodCallExpression.getArgumentList();
            PsiExpression[] expressions = argumentList.getExpressions();
            for (int i = 0; i < expressions.length; i++) {
                PsiExpression expression = expressions[i];
                PsiParameter param = parameters[i];
                PsiType type = param.getType();
                if (i > 0) {
                    sb.append(", ");
                }
                appendComparison(sb, type, expression.getText());
            }
            sb.append(")");
        }
        PsiElementFactory elementFactory = statement.getManager().getElementFactory();
        if (returnType == null) {
            returnType = psiMethod.getReturnType();
        }
        if (returnType != PsiType.VOID) {
            sb.append(".will(");
            if (returnTypeName == null) {
                sb.append("doThis");
            } else {
                if (createReturnType) {
                    PsiStatement statementFromText = elementFactory.createStatementFromText(returnType.getPresentableText() + " " + returnTypeName + ";", statement);
                    statement.getParent().addBefore(statementFromText, statement);
                }
                sb.append("returnValue(").append(returnTypeName).append(")");
            }
            sb.append(")");
        }
        sb.append(";");
        PsiStatement statementFromText = elementFactory.createStatementFromText(sb.toString(), statement);
        statement.replace(statementFromText);
    }

    public boolean startInWriteAction() {
        return true;
    }

    private String lcFirst(String text) {
        if (text == null) {
            return null;
        }
        if (text.endsWith("[]")) {
            text = text.substring(0, text.length() - 2) + "s";
        }
        if (text.length() == 0) {
            return "";
        }
        if (text.charAt(0) >= 'a' && text.charAt(0) <= 'z') {
            return text + "Value";
        }
        return text.substring(0, 1).toLowerCase() + text.substring(1);
    }

    private void appendComparison(StringBuffer sb, PsiType type, String text) {
        if (type instanceof PsiClassType &&
                !type.getCanonicalText().equals("java.lang.String") &&
                !type.getCanonicalText().equals("java.lang.Class") &&
                !text.startsWith("new ")) {
            sb.append("same");
        } else {
            sb.append("eq");
        }
        sb.append("(");
        sb.append(text);
        sb.append(")");
    }
}
